﻿/*************************************************************************************
 * 工    具：  HK_UIBorderPreview
 * 
 * 描    述：   本工具用于在 Scene 视图中实时可视化 UI 元素的边界框，主要功能如下：
 *             边界框绘制：为场景中的 UI 元素（Image/RawImage/Text 等）实时绘制彩色边框
 *             尺寸信息显示：在边框附近显示 UI 元素的名称、尺寸和锚点位置信息
 *             交互组件抑制：自动跳过包含 Button/Slider 等交互组件的子级可视化
 *             动态颜色配置：通过 HK_UIBorderPreviewWindow 配置面板自定义边框颜色和线宽
 * 
 * 版    本：  V1.0
 * 作    者：  王凯、京产肠饭
 * 
 * 创    建：  2025/04/23  V1.0
 * 
 * 链    接：  https://gitee.com/JingChanChangFan/hk_-unity-tools
*************************************************************************************/

using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;

namespace HKTools
{
    /// <summary>
    /// 处理在 Scene 视图中绘制 UI 元素边框和信息的静态类。
    /// 使用 [InitializeOnLoad] 确保编辑器启动时注册 SceneGUI 委托。
    /// </summary>
    [InitializeOnLoad]
    public static class HK_UIBorderPreview
    {
        // 用于存储 RectTransform 世界坐标的缓存数组
        static readonly Vector3[] worldCorners = new Vector3[4];
        // 用于存储 RectTransform 屏幕坐标的缓存数组 (主要用于检查是否在相机前方)
        static readonly Vector3[] screenCorners = new Vector3[4];

        /// <summary>
        /// 定义一组交互式 UI 组件类型。
        /// 如果 GameObject 上存在这些类型的组件，则其子级的 Image/RawImage 将不会被绘制边框。
        /// </summary>
        static readonly HashSet<System.Type> interactiveTypes = new HashSet<System.Type>
        {
            typeof(Button),
            typeof(Slider),
            typeof(Toggle),
            typeof(Scrollbar),
            typeof(ScrollRect),
            typeof(InputField) // 也考虑 InputField
            // 如果需要，添加其他交互类型
        };

        /// <summary>
        /// 静态构造函数，在编辑器加载时调用。
        /// </summary>
        static HK_UIBorderPreview()
        {
            // 使用 EditorApplication.update 稍微延迟注册，
            // 以增加 UIBorderPreviewWindow 初始化设置的机会。
            EditorApplication.delayCall += RegisterSceneGUIDelegateOnce;
        }

        /// <summary>
        /// 仅执行一次的委托注册方法。
        /// </summary>
        static void RegisterSceneGUIDelegateOnce()
        {
            // 先取消订阅以防止重复注册
            SceneView.duringSceneGui -= OnSceneGUI;
            SceneView.duringSceneGui += OnSceneGUI;
        }

        /// <summary>
        /// SceneView 的绘制回调方法。
        /// </summary>
        static void OnSceneGUI(SceneView sceneView)
        {
            // 每帧获取一次设置
            UIBorderPreviewSettings settings = HK_UIBorderPreviewWindow.GetCurrentSettings();

            // 基本检查：工具是否已启用？
            if (!settings.isToolEnabled || settings.borderThickness <= 0f)
                return;

            // 获取场景相机和视锥体平面
            Camera sceneCamera = sceneView.camera;

            if (sceneCamera == null)
                return;

            Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(sceneCamera);

            // 查找场景中所有活动的根 Canvas
            Canvas[] rootCanvases = Object.FindObjectsByType<Canvas>(FindObjectsSortMode.None).Where(c => c.isRootCanvas && c.gameObject.activeInHierarchy).ToArray();

            // 预先计算哪些 GameObject 带有交互组件
            HashSet<GameObject> objectsWithInteractiveComponent = new HashSet<GameObject>(
                Object.FindObjectsByType<RectTransform>(FindObjectsSortMode.None) // 查找所有 RectTransform
                          .Where(rt => HasInteractiveComponent(rt.gameObject)) // 检查它们是否有交互组件
                          .Select(rt => rt.gameObject) // 选择 GameObject
            );

            // 遍历每个根 Canvas 层级
            foreach (var canvas in rootCanvases)
            {
                TraverseNode(canvas.transform, objectsWithInteractiveComponent, sceneCamera, frustumPlanes, settings);
            }
        }

        /// <summary>
        /// 递归地遍历 UI 节点及其子节点，查找并绘制符合条件的元素边框。
        /// </summary>
        /// <param name="node">当前处理的 Transform 节点。</param>
        /// <param name="objectsWithInteractiveComponent">包含交互组件的 GameObject 集合。</param>
        /// <param name="sceneCamera">场景相机。</param>
        /// <param name="frustumPlanes">场景相机视锥体平面。</param>
        /// <param name="settings">当前的预览设置。</param>
        static void TraverseNode(Transform node, HashSet<GameObject> objectsWithInteractiveComponent, Camera sceneCamera, Plane[] frustumPlanes, UIBorderPreviewSettings settings)
        {
            RectTransform rt = node as RectTransform;
            // 跳过非 RectTransform 或非激活节点
            if (rt == null || !node.gameObject.activeInHierarchy) return;

            // 需求 2：检查此节点或其父节点是否包含交互组件
            bool isInteractivePresentOnSelf = objectsWithInteractiveComponent.Contains(node.gameObject);
            bool isInteractivePresentOnParent = false;
            Transform currentParent = node.parent;
            while (currentParent != null)
            {
                if (objectsWithInteractiveComponent.Contains(currentParent.gameObject)) { isInteractivePresentOnParent = true; break; }

                // 优化：如果遇到另一个 Canvas，是否停止向上搜索？取决于期望的行为。
                currentParent = currentParent.parent;
            }

            // --- 重构的组件选择和抑制逻辑 ---
            MonoBehaviour componentToDraw = null;
            HK_UIBorderPreviewWindow.ComponentSetting settingToUse = null;

            // 1. 根据优先级和设置查找最佳候选组件
            MonoBehaviour bestCandidateComp = null;
            HK_UIBorderPreviewWindow.ComponentSetting bestCandidateSetting = null;
            int bestPriority = -1; // 优先级：-1=无, 0=图形(Image/Text/...), 1=交互(Button/...)

            MonoBehaviour[] components = node.GetComponents<MonoBehaviour>();
            foreach (var currentComp in components)
            {
                if (currentComp == null) continue; // 跳过空组件
                System.Type currentCompType = currentComp.GetType();

                // 检查此组件类型是否在设置中启用
                if (settings.TryGetEnabledComponentSetting(currentCompType, out var currentSetting))
                {
                    int currentPriority = -1;
                    if (interactiveTypes.Contains(currentCompType))
                    {
                        currentPriority = 1; // 交互组件具有最高优先级
                    }
                    // 在检查交互组件之后检查标准图形，因为交互组件可能有图形子项
                    else if (currentCompType == typeof(Image) ||
                             currentCompType == typeof(RawImage) ||
                             currentCompType == typeof(Text) ||
                             (HK_UIBorderPreviewWindow.IsTextMeshProAvailable && currentCompType == HK_UIBorderPreviewWindow.TextMeshProUGUIType))
                    {
                        // 仅当尚未找到更高优先级的组件时才考虑图形组件
                        if (bestPriority < 0) currentPriority = 0;
                    }
                    // 如果需要，添加对其他组件类型的检查

                    // 如果此组件的优先级高于当前最佳组件，则选择它
                    if (currentPriority > bestPriority)
                    {
                        bestCandidateComp = currentComp;
                        bestCandidateSetting = currentSetting;
                        bestPriority = currentPriority;
                    }
                }
            }

            // 2. 检查所选候选组件的抑制条件（如果有）
            if (bestCandidateComp != null && bestCandidateSetting != null)
            {
                System.Type bestCompType = bestCandidateComp.GetType();
                // 检查最佳候选组件是否是应被抑制的类型
                bool isGraphicToSuppress = bestCompType == typeof(Image) ||
                                           bestCompType == typeof(RawImage) ||
                                           bestCompType == typeof(Text) ||
                                           (HK_UIBorderPreviewWindow.IsTextMeshProAvailable && bestCompType == HK_UIBorderPreviewWindow.TextMeshProUGUIType);

                // 3. 应用抑制：如果它是可抑制的图形组件 并且 交互组件存在于自身/父级上，则不绘制它。
                if (!(isGraphicToSuppress && (isInteractivePresentOnSelf || isInteractivePresentOnParent)))
                {
                    // 如果抑制不适用，则这是要绘制的组件
                    componentToDraw = bestCandidateComp;
                    settingToUse = bestCandidateSetting;
                }
                // 否则：抑制适用，componentToDraw 保持为 null
            }
            // --- 结束重构逻辑 ---


            // 如果我们找到了有效的要绘制的组件（抑制检查之后）
            if (componentToDraw != null && settingToUse != null)
            {
                rt.GetWorldCorners(worldCorners); // 获取世界坐标角点
                DrawBorder(rt, settingToUse, sceneCamera, settings.borderThickness, settings);  // 调用绘制函数
            }

            // 递归处理子节点
            foreach (Transform child in node)
            {
                TraverseNode(child, objectsWithInteractiveComponent, sceneCamera, frustumPlanes, settings);
            }
        }

        /// <summary>
        /// 检查 GameObject 是否包含任何指定的交互式 UI 组件。
        /// </summary>
        /// <param name="go">要检查的 GameObject。</param>
        /// <returns>如果找到交互式组件，则返回 true。</returns>
        static bool HasInteractiveComponent(GameObject go)
        {
            foreach (var type in interactiveTypes)
            {
                if (go.GetComponent(type) != null)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 绘制 RectTransform 的边框和（可选的）详细信息。
        /// (修改：根据后续兄弟节点的包围盒重叠近似判断遮挡)
        /// </summary>
        /// <param name="rt">要绘制的 RectTransform。</param>
        /// <param name="setting">与此元素关联的组件设置 (包含颜色)。</param>
        /// <param name="sceneCamera">场景相机。</param>
        /// <param name="thickness">边框线宽。</param>
        /// <param name="showDetails">是否显示详细信息。</param>
        static void DrawBorder(RectTransform rt, HK_UIBorderPreviewWindow.ComponentSetting setting, Camera sceneCamera, float thickness, UIBorderPreviewSettings settings)
        {
            Color color = setting.color; // 获取颜色
            rt.GetWorldCorners(worldCorners); // 获取世界坐标顶点

            // 遮挡检查（基于后续兄弟节点的近似判断） ---
            bool isOccluded = false;
            Transform parent = rt.parent;

            //仅当存在父节点和潜在的兄弟节点时才执行检查
            if (parent != null)
            {
                Bounds targetBounds = GeometryUtility.CalculateBounds(worldCorners, Matrix4x4.identity);
                int siblingIndex = rt.GetSiblingIndex();
                for (int i = siblingIndex + 1; i < parent.childCount; i++)
                {
                    Transform sibling = parent.GetChild(i);
                    RectTransform siblingRt = sibling as RectTransform;
                    // 仅检查活动的 RectTransform 兄弟节点
                    if (siblingRt != null && sibling.gameObject.activeInHierarchy)
                    {
                        Vector3[] siblingCorners = new Vector3[4];
                        siblingRt.GetWorldCorners(siblingCorners);
                        Bounds siblingBounds = GeometryUtility.CalculateBounds(siblingCorners, Matrix4x4.identity);
                        // 简单的包围盒相交检查
                        if (siblingBounds.Intersects(targetBounds))
                        {
                            // 假设相交意味着可能被后面绘制的兄弟节点遮挡
                            isOccluded = true;
                            break; // 找到了一个潜在的遮挡物，无需进一步检查
                        }
                    }
                }
            }
            // --- 结束遮挡检查 ---

            // 保存 Handles 原始状态
            Color originalColor = Handles.color;

            try
            {
                // 设置绘制状态
                Handles.color = color;

                // 检查是否可以绘制详细信息 (至少一个角点在相机前方)
                bool canDrawDetails = false;
                for (int i = 0; i < 4; ++i)
                {
                    screenCorners[i] = sceneCamera.WorldToScreenPoint(worldCorners[i]);
                    if (screenCorners[i].z > 0) { canDrawDetails = true; }
                    if (screenCorners[i].z < 0) { canDrawDetails = false; break; } // 如果有角点在后面，则不绘制详情
                }

                //---- 根据遮挡绘制线条 --- 
                if (!isOccluded)
                {
                    // 未被遮挡 (或无后续兄弟遮挡): 绘制实线
                    Handles.DrawAAPolyLine(thickness,
                    worldCorners[0], worldCorners[1],
                    worldCorners[1], worldCorners[2],
                    worldCorners[2], worldCorners[3],
                    worldCorners[3], worldCorners[0]);
                }
                else
                {
                    // 被后续兄弟遮挡 (近似): 绘制虚线
                    DrawDottedPolyLine(Mathf.Max(1.0f, thickness / 2f) * 2f, worldCorners[0], worldCorners[1], worldCorners[2], worldCorners[3], worldCorners[0]);
                }
                // --- 结束绘制线条 ---

                // 绘制详细信息 (如果启用且可绘制)
                if (canDrawDetails)
                {
                    var originalGuiColor = GUI.color;
                    try
                    {
                        string name = "";
                        string size = "";
                        string anchoredPos = "";

                        if (settings.isShowName)
                            name = $"{rt.gameObject.name}\n";

                        if (settings.isShowSize)
                            size = $"Size: {rt.rect.size.x:F1}x{rt.rect.size.y:F1}\n";

                        if (settings.isShowAnchorPos)
                            anchoredPos = $"AnchPos: {rt.anchoredPosition.x:F1},{rt.anchoredPosition.y:F1}";

                        GUI.color = color; // 设置 GUI 颜色以匹配 Handles 颜色
                        string details = name + size + anchoredPos;
                        Vector3 labelPosition = worldCorners[1]; // 左上角
                                                                 // 稍微偏移标签以避免遮挡边框线
                        Vector3 viewVector = (labelPosition - sceneCamera.transform.position).normalized;
                        float handleSize = HandleUtility.GetHandleSize(labelPosition);
                        labelPosition += viewVector * handleSize * 0.1f;
                        // 创建并设置标签样式
                        GUIStyle style = new GUIStyle();
                        style.normal.textColor = color;
                        style.fontSize = 10;
                        style.alignment = TextAnchor.UpperLeft;
                        // 绘制标签
                        Handles.Label(labelPosition, details, style);
                    }
                    finally
                    {
                        GUI.color = originalGuiColor; // 恢复原始 GUI 颜色
                    }
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError($"[DrawBorder-{rt.name}] Error: {e.Message}\n{e.StackTrace}"); // 同时记录堆栈跟踪
            }
            finally
            {
                // 恢复 Handles 状态（仅颜色）
                Handles.color = originalColor;
            }
        }

        // 如果之前移除了 DrawDottedPolyLine 函数，则恢复它
        /// <summary>
        /// 绘制虚线多边形。
        /// </summary>
        /// <param name="screenSpaceSize">虚线的点/间隔的大小 (屏幕空间)。</param>
        /// <param name="points">定义多边形闭环的顶点。</param>
        static void DrawDottedPolyLine(float screenSpaceSize, params Vector3[] points)
        {
            if (points.Length < 2) return; // 至少需要 2 个点
            for (int i = 0; i < points.Length; i++)
            {
                // 绘制到下一个点（环绕）的线段
                Handles.DrawDottedLine(points[i], points[(i + 1) % points.Length], screenSpaceSize);
            }
        }
    }
}